package simmar.input;

import simmar.input.InputKnowledge;
import ch.idsia.benchmark.mario.environments.Environment;
import ch.idsia.benchmark.mario.engine.GeneralizerLevelScene;
import ch.idsia.benchmark.mario.engine.sprites.Sprite;

public class InputLibrary {
	
	/**
	 * Get if right is clear
	 * @param knowledge - memorized knowledge of the agent
	 * @param environment - the current environment
	 * @return true if adjacent cell to the right is clear and safe
	 */
	public static double getBinaryClearRight(InputKnowledge knowledge, Environment environment) {
		int[] center = environment.getMarioEgoPos();
		byte grid[][] = environment.getMergedObservationZZ(1, 2);
		
		// TODO: Account for large mario's size
		
		switch (grid[center[0]][center[1]+1]) {
			case 0:
			case GeneralizerLevelScene.COIN_ANIM:
			case GeneralizerLevelScene.BORDER_HILL:
				return 1.0;
			default:
				return 0.0;
		}
	}
	
	/**
	 * Get if left is clear
	 * @param knowledge - memorized knowledge of the agent
	 * @param environment - the current environment
	 * @return true if adjacent cell to the left is clear and safe
	 */
	public static double getBinaryClearLeft(InputKnowledge knowledge, Environment environment) {
		int[] center = environment.getMarioEgoPos();
		byte grid[][] = environment.getMergedObservationZZ(1, 2);
		
		// TODO: Account for large mario's size
		
		switch (grid[center[0]][center[1]-1]) {
			case 0:
			case GeneralizerLevelScene.COIN_ANIM:
			case GeneralizerLevelScene.BORDER_HILL:
				return 1.0;
			default:
				return 0.0;
		}
	}
	
	/**
	 * Get if up is clear
	 * @param knowledge - memorized knowledge of the agent
	 * @param environment - the current environment
	 * @return true if adjacent cell above is clear and safe
	 */
	public static double getBinaryClearAbove(InputKnowledge knowledge, Environment environment) {
		int[] center = environment.getMarioEgoPos();
		byte grid[][] = environment.getMergedObservationZZ(1, 2);
		
		// TODO: Check enemy type for enemies that can be defeated from below
		
		switch (grid[center[0]-1][center[1]]) {
			case 0:
			case GeneralizerLevelScene.COIN_ANIM:
			case GeneralizerLevelScene.BORDER_HILL:
				return 1.0;
			default:
				return 0.0;
		}
	}
	
	/**
	 * Get if down is clear
	 * @param knowledge - memorized knowledge of the agent
	 * @param environment - the current environment
	 * @return true if adjacent cell below is clear and safe
	 */
	public static double getBinaryClearBelow(InputKnowledge knowledge, Environment environment) {
		int[] center = environment.getMarioEgoPos();
		byte grid[][] = environment.getMergedObservationZZ(1, 2);
		
		// TODO: Check enemy type for enemies that can be defeated from above
		
		switch (grid[center[0]+1][center[1]]) {
			case 0:
			case GeneralizerLevelScene.COIN_ANIM:
			case GeneralizerLevelScene.BORDER_HILL:
				return 1.0;
			default:
				return 0.0;
		}
	}

	/**
	 * Get if Mario can ascend by jumping
	 * @param knowledge
	 * @param environment
	 * @return true if maximum jump height hasn't been reached yet or Mario can jump
	 */
	public static double getBinaryCanAscend(InputKnowledge knowledge, Environment environment) {
		if (knowledge.marioJumping) {
			if (knowledge.marioJumpCounter < InputKnowledge.marioJumpLimit) {
				return 1.0;
			} else {
				return 0.0;
			}
		} else {
			if (knowledge.marioCanJump) {
				return 1.0;
			} else {
				return 0.0;
			}
		}
	}

	/**
	 * Get if there's an enemy to the right of Mario within 3 cells
	 * @param knowledge
	 * @param environment
	 * @return true if there's an enemy
	 */
	public static double getBinaryEnemyAhead(InputKnowledge knowledge, Environment environment) {
		int[] center = environment.getMarioEgoPos();
		byte grid[][] = environment.getMergedObservationZZ(1, 2);
		
		for (int i = 1; i<=3; i++) {
			switch (grid[ center[0] ][ center[1]+i ]) {
				case 1:
					return 1.0;
				default:
			}
		}
		
		return 0.0;
	}
	
	/**
	 * Get if there's an obstacle to the right of Mario within 5 cells
	 * @param knowledge
	 * @param environment
	 * @return true if there's an obstacle
	 */
	public static double getBinaryObstacleAhead(InputKnowledge knowledge, Environment environment) {
		int[] center = environment.getMarioEgoPos();
		byte grid[][] = environment.getMergedObservationZZ(1, 2);
		
		for (int i = 1; i<=5; i++) {
			boolean clear = false;
			
			switch (grid[ center[0] ][ center[1]+i ]) {
				case 0:
				case GeneralizerLevelScene.COIN_ANIM:
				case GeneralizerLevelScene.BORDER_HILL:
				case 1:
					clear = true;
					break;
				default:
					return 1.0;
			}
			
			if (clear) {
				switch (grid[ center[0]+1 ][ center[1]+i ]) {
				case 0:
				case GeneralizerLevelScene.COIN_ANIM:
				case GeneralizerLevelScene.BORDER_HILL:
				case 1:
					return 0.0;
				default:
				}
			}
		}
		
		return 0.0;
	}
	
	/**
	 * Get if there's a pit to the right of Mario within 5 cells that can only be passed by jumping
	 * @param knowledge
	 * @param environment
	 * @return true if there's a pit
	 */
	public static double getBinaryPitAhead(InputKnowledge knowledge, Environment environment) {
		int[] center = environment.getMarioEgoPos();
		byte grid[][] = environment.getMergedObservationZZ(1, 2);
		
		for (int i = 1; i<=5; i++) {
			for (int j = 0; (center[0]+j)<grid.length; j++) {
				switch (grid[ center[0]+j ][ center[1]+i ]) {
					case 0:
					case GeneralizerLevelScene.COIN_ANIM:
					case GeneralizerLevelScene.BORDER_HILL:
						return 1.0;
					default:
				}
			}
		}
		
		return 0.0;
	}
	
	/**
	 * Get if there's a pit or dangerous enemy below mario
	 * @param knowledge
	 * @param environment
	 * @return true if there's a danger
	 */
	public static double getBinaryDangerBelow(InputKnowledge knowledge, Environment environment) {
		int[] center = environment.getMarioEgoPos();
		byte grid[][] = environment.getMergedObservationZZ(1, 0);
		
		for (int i = 1; (center[0]+i)<grid.length; i++) {
			switch (grid[ center[0]+i ][ center[1] ]) {
				case 0:
				case GeneralizerLevelScene.COIN_ANIM:
				case GeneralizerLevelScene.BORDER_HILL:
					break;
				case Sprite.KIND_ENEMY_FLOWER:
				case Sprite.KIND_SPIKY:
				case Sprite.KIND_SPIKY_WINGED:
					return 1.0;
				default:
					return 0.0;
			}
		}
		
		return 1.0;
	}
	
	/**
	 * Get if there's an enemy lined up for a shot and mario can shoot
	 * @param knowledge
	 * @param environment
	 * @return true if there's an enemy lined up
	 */
	public static double getBinaryShotAimed(InputKnowledge knowledge, Environment environment) {
		if (knowledge.marioCanShoot) {
			int[] center = environment.getMarioEgoPos();
			byte grid[][] = environment.getMergedObservationZZ(1, 2);
			
			if (grid[ center[0] ][ center[1]+1 ] == 1) {
				return 1.0;
			} else if (grid[ center[0]+1 ][ center[1]+1 ] == 1) {
				return 1.0;
			} else if (grid[ center[0]+1 ][ center[1]+2 ] == 1) {
				return 1.0;
			} else {
				return 0.0;
			}
		} else {
			return 0.0;
		}
	}
	
	/**
	 * Dummy function
	 * @param knowledge - memorized knowledge of the agent
	 * @param environment - the current environment
	 * @return always true
	 */
	public static double getBinaryDummy(InputKnowledge knowledge, Environment environment) {
		switch (Sprite.KIND_MARIO) {
			case Sprite.KIND_MARIO:
				return 1.0;
			default:
				return 0.0;
		}
	}
	
}
